<?php

/**
 * datagram packet protocol:
 * crc          : 32bit crc32 value of all fields excluding itself,
 *                including 'identifier', 'length', 'type', 'reversed', 'body'
 * identifier   : 32bit unsigned int,
 *                the request is same as the response
 * length       : 32bit unsigned int,
 *                just means the size of 'body' field
 * type         : 8bit char,
 *                request type is [0,128],  [0,0x0F] is for system need,
 *                response type is (128,256],
 *                [response type] = [response type] | (0x01 << 7)
 *                defined types as following:
 *                0x01  - heartbeat request
 *                0x10  - business request
 * reversed     : 24bit char[3], padding with '\0'
 * body         : bytes' count indicated by the 'length' field,
 *                for the better performance,
 *                if this is request, max value is 8192 (8k),
 *                if this is response, max value is 81920 (80k)
 *
 * command:
 * php -c php.ini -f app/wroot/SoaServer.php
 *
 *
 */
date_default_timezone_set('Asia/Shanghai');

/**
 * set 'dev' or 'beta' or 'prod'
 *
 * @var unknown
 */
define('PROFILE', 'beta');

define('ROOT_PATH', dirname(dirname(dirname(__FILE__))));
define('VENDOR_PATH', ROOT_PATH . DIRECTORY_SEPARATOR . 'vendor');
define('APP_PATH', ROOT_PATH . DIRECTORY_SEPARATOR . 'app');
define('LOG_PATH', ROOT_PATH . DIRECTORY_SEPARATOR . 'logs');

define('PID_FILE', LOG_PATH . DIRECTORY_SEPARATOR . 'mypid');
define('NODE_FILE', LOG_PATH . DIRECTORY_SEPARATOR . 'node');
define('SWOOLE_LOG_FILE', LOG_PATH . DIRECTORY_SEPARATOR . 'swoole.log');
// define('ZK_ADDRS', '');
define('SERVICES_ROOT_PATH', ROOT_PATH . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'services');
define('NODE_CONFIG_FILE', ROOT_PATH . DIRECTORY_SEPARATOR . "node.json");
define('SERVICES_CONFIG_FILE', ROOT_PATH . DIRECTORY_SEPARATOR . "services.json");

require_once APP_PATH . DIRECTORY_SEPARATOR . 'common' . DIRECTORY_SEPARATOR . 'error.php';
require_once APP_PATH . DIRECTORY_SEPARATOR . 'common' . DIRECTORY_SEPARATOR . 'constants.php';
require_once APP_PATH . DIRECTORY_SEPARATOR . 'common' . DIRECTORY_SEPARATOR . 'functions.php';
require_once APP_PATH . DIRECTORY_SEPARATOR . 'common' . DIRECTORY_SEPARATOR . 'profile.' . PROFILE . '.php';

if(merge_config_files(APP_PATH, SERVICES_ROOT_PATH, NODE_CONFIG_FILE, SERVICES_CONFIG_FILE) < 0){
	soa_error('Merge config files failed!');
	soa_error('SOA server startup failed!');
	exit();
}
init_service_intances(SERVICES_CONFIG_FILE);
parse_node_key(SERVICES_CONFIG_FILE);

/**
 *
 * @author Kevin <xuhuahai@dangdang.com>
 * @since Apr 13, 2015 12:39:19 PM
 * @version 1.0
 * @copyright (c) 2015 , DANG
 *
 */
class SoaServer {
	private $serv;
	public function __construct() {
		$this->serv = new swoole_server(get_host(SERVICES_CONFIG_FILE), get_port(SERVICES_CONFIG_FILE));
		$this->serv->set(array(
			'open_tcp_nodelay' => true,
			'worker_num' => 20,
			'daemonize' => true,
			'log_file' => SWOOLE_LOG_FILE,
			'max_request' => 80000,
			'dispatch_mode' => 2,
			'package_max_length' => 81920,
			'open_length_check' => true,
			'package_length_offset' => 8, // lengh field offset
			'package_body_offset' => 16, // head lengh is 16 bytes
			'package_length_type' => 'N',
		));

		$this->serv->on('Start', array(
			$this,
			'onStart',
		));
		$this->serv->on('Shutdown', array(
				$this,
				'onShutdown',
		));
		$this->serv->on('Connect', array(
			$this,
			'onConnect',
		));
		$this->serv->on('Receive', array(
			$this,
			'onReceive',
		));
		$this->serv->on('Close', array(
			$this,
			'onClose',
		));
		$this->serv->on('workerStart', array(
			$this,
			'onWorkerStart',
		));
	}
	public function start() {
		$this->serv->start();
	}
	public function onStart($serv) {
		soa_info('SOA server start ...');
		$ret = register_service_by_file('', SERVICES_CONFIG_FILE);
		if($ret == -1){
			soa_info('zk services registered failed');
			$serv->shutdown();
			return;
		}
		soa_info('zk services registered successfully');
		scp_write_node_value(NODE_FILE);
		//write pid
		$pid = getmypid();
		$myfile = fopen(PID_FILE, "w") or die("Unable to open file!");
		fwrite($myfile, $pid . "");
		fclose($myfile);
		soa_info('SOA server started! - '.scp_get_thread_id());
	}
	function onShutdown($serv)
	{
		soa_info('SOA server stop ...');
		
		soa_info('zk services unregistered');
		soa_info('SOA server stopped!');
	}
	public function onConnect($serv, $fd, $from_id) {
		soa_info('Client ' . $fd . ' establish connection');
	}
	public function onReceive(swoole_server $serv, $fd, $from_id, $data) {
		//解析报文头
		$header = unpack("N3f/C4t", $data);
		$crc = $header['f1'];
		$identifier = $header['f2'];
		$length = $header['f3'];
		$type = $header['t1'];
		
		//CRC32检查
		$crc_req = crc32(substr($data, 4));
		if ($crc_req != $crc) {
			soa_error('invoke service failed : crc check failed');
			$serv->close($fd);  //链路上的数据已经无效，关闭该连接
			return;
		}

		//解码报文体
		$body = substr($data, 16);
		$req = json_decode($body, TRUE);
		$service_group = $req['group'];
		$service_name = $req['service'];
		$service_version = $req['version'];
		$trace_id = $req['trace_id'];
		$trace_span_id = $req['trace_span_id'];
		$request_params = $req['data'];
		
		//调用本地服务实例
		$resp = \core\ServiceInvoke::invokeLocalSoaService($service_group, $service_name, $service_version, 
				$trace_id, $trace_span_id, $request_params);
		if (is_null($resp)){
			soa_error('invoke service failed : not found service : ' . $service_group . ',' . $service_name . ',' . $service_version );
			$resp = get_status_code('1');
		}
		
		//编码应答报文体
		$resp = json_encode($resp);
		$resp_len = strlen($resp);
		$response = pack("NNCCCC", $identifier, $resp_len, ($type | (1 << 7)), 0, 0, 0);
		$response .= $resp;
		$response = pack("N", crc32($response)) . $response;
		
		//发送应答
		$serv->send($fd, $response);
		
// 		$time_cost = (get_now_microtime_float() - $time_start) * 1000;
// 		soa_info('invoke service success : ' . $service_group . ',' . $service_name . ',' . $service_version. ' # cost time (ms) : '. $time_cost . ' ');
	}
	public function onClose($serv, $fd, $from_id) {
		soa_info('Client ' . $fd . ' close connection');
	}
	public function onWorkerStart($serv, $worker_id) {
		soa_info('Worker ' . $worker_id . ' start');
	}
}

$server = new SoaServer();
require VENDOR_PATH . DIRECTORY_SEPARATOR . 'autoload.php';
soa_info('register autoload');

$server->start();
